package ace.cmp.security.oauth2.resource.server.core.introspection;

import ace.cmp.json.api.JsonService;
import ace.cmp.security.core.impl.constant.Oauth2Constant;
import ace.cmp.security.core.impl.model.dto.Oauth2UserInfoDto;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;

/**
 * @author caspar ACE 架构服务端验证token
 * 支持特性：
 * 1、验证token,不带用户信息。使用场景：Client Credentials
 * 2、验证token,带用户信息与权限。使用场景：会员认证
 */
@Slf4j
public class UserInfoOpaqueTokenIntrospector implements OpaqueTokenIntrospector {

  /**
   * access token 真实认证的 {@link OpaqueTokenIntrospector}
   */
  private final OpaqueTokenIntrospector delegatingOpaqueTokenIntrospector;

  private final JsonService jsonService;

  public UserInfoOpaqueTokenIntrospector(
      OpaqueTokenIntrospector delegatingOpaqueTokenIntrospector, JsonService jsonService) {
    this.delegatingOpaqueTokenIntrospector = delegatingOpaqueTokenIntrospector;
    this.jsonService = jsonService;
  }

  @Override
  public OAuth2AuthenticatedPrincipal introspect(String tokenValue) {
    OAuth2AuthenticatedPrincipal authorized = delegatingOpaqueTokenIntrospector.introspect(tokenValue);
    if (authorized
        .getAttributes()
        .containsKey(Oauth2Constant.TOKEN_CLAIMS_USERINFO_ATTRIBUTE_KEY)
    ) {
      Map<String, String> userInfoMap = authorized.getAttribute(Oauth2Constant.TOKEN_CLAIMS_USERINFO_ATTRIBUTE_KEY);
      if (MapUtils.isEmpty(userInfoMap)) {
        return authorized;
      }
      Oauth2UserInfoDto userInfoDto = this.jsonService.toObject(jsonService.toJson(userInfoMap), Oauth2UserInfoDto.class);

      List<GrantedAuthority> authorities = new ArrayList<>(authorized.getAuthorities());

      List<GrantedAuthority> userInfoAuthorities = Optional.ofNullable(userInfoDto)
          .map(Oauth2UserInfoDto::getAuthorities)
          .stream()
          .flatMap(Set::stream)
          .map(SimpleGrantedAuthority::new)
          .collect(Collectors.toList());

      authorities.addAll(userInfoAuthorities);

      Map<String, Object> attributes = new HashMap<>();

      attributes.putAll(authorized.getAttributes());

      Map<String, String> userInfoAttributes = Optional.ofNullable(userInfoDto)
          .map(p -> p.getAttributes())
          .map(p -> p.entrySet())
          .stream()
          .flatMap(Set::stream)
          .collect(
              HashMap::new,
              (m, id) -> {
                m.put(id.getKey(), id.getValue());
              },
              HashMap::putAll
          );

      attributes.putAll(userInfoAttributes);

      DefaultOAuth2AuthenticatedPrincipal userInfoPrincipal = new DefaultOAuth2AuthenticatedPrincipal(userInfoDto.getId(), attributes, authorities);

      return userInfoPrincipal;
    }
    return authorized;
  }
}
